This Rmarkdown file assesses the output of CheckV, DeepVirFinder, Kaiju, VIBRANT, VirSorter, and VirSorter2 on multiple training sets of microbial DNA, primarily from NCBI. Created from fungal, viral, bacterial, archeael, protist, and plasmid DNA sequences

Please reach out to James Riddell () or Bridget Hegarty () regarding any issues, or open an issue on github.

library(pROC)
Type 'citation("pROC")' for a citation.

Attaching package: ‘pROC’

The following objects are masked from ‘package:stats’:

    cov, smooth, var

Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
2: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
3: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
4: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
5: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
6: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
7: Unknown or uninitialised column: `keep_score_visualize`. 
8: Unknown or uninitialised column: `truepositive`. 

Import the file that combines the results from each of the tools from running “combining_tool_output.Rmd”:

viruses <- read_tsv("../IntermediaryFiles/viral_tools_combined.tsv")

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  .default = col_double(),
  seqtype = col_character(),
  contig = col_character(),
  checkv_provirus = col_character(),
  checkv_quality = col_character(),
  method.x = col_character(),
  Classified = col_character(),
  IDs_all = col_character(),
  Seq = col_character(),
  Kaiju_Viral = col_character(),
  Kingdom = col_character(),
  type = col_character(),
  vibrant_quality = col_character(),
  method.y = col_character(),
  vibrant_prophage = col_character(),
  vs2type = col_character(),
  max_score_group = col_character()
)
ℹ Use `spec()` for the full column specifications.

This section defines a viralness score “keep_score” based on the tool classifications. A final keep_score above 1 indicates we will keep that sequence and call it viral.

VIBRANT Quality == “High Quality Draft”: +1 Quality == “Medium Quality Draft”: +1 Quality == “Low Quality Draft” & provirus == TRUE: +0.5

Virsorter2 Viral >= 50: +0.5 Viral >= 0.95: +0.5

Virsorter category == 1,2,4,5: +1 category == 3,6: +0.5

DeepVirFinder: Score >= 0.7: +0.5 Score >= 0.9: +0.5

Kaiju: Kaiju_viral = “cellular organisms”: -1 Kaiju_viral = “Viruses”: +1

CheckV If %unknown >= 75: +0.5 Hallmark > 2: +1 viral_genes == 0 and host_genes >= 1: keep_score = 0 If 3*viral_genes <= host_genes: keep_score = 0 If length > 50,000 and hallmark == 0: keep_score = 0

This script produces visualizations of these combined viral scorings and includes ecological metrics like alpha diversity.

You can decide which combination is appropriate for them and only need use the tools appropriate for your data.

getting_viral_set_1 <- function(input_seqs,
                                include_vibrant=FALSE, 
                                include_virsorter2=FALSE,
                                include_deepvirfinder=FALSE,
                                include_tuning=FALSE,
                                include_kaiju=FALSE,
                                include_virsorter=FALSE) {
  
  keep_score <- rep(0, nrow(input_seqs))
  
  if (include_vibrant) {
    keep_score[input_seqs$vibrant_quality=="high quality draft"] <- keep_score[input_seqs$vibrant_quality=="high quality draft"] + 1
    keep_score[input_seqs$vibrant_quality=="medium quality draft"] <- keep_score[input_seqs$vibrant_quality=="medium quality draft"] + 1
    keep_score[input_seqs$vibrant_quality=="low quality draft" & input_seqs$checkv_provirus=="Yes"] <- keep_score[input_seqs$vibrant_quality=="low quality draft" & input_seqs$checkv_provirus=="Yes"] + 0.5
#    keep_score[input_seqs$vibrant_quality=="low quality draft"] <- keep_score[input_seqs$vibrant_quality=="low quality draft"] + 0.5
  }
  
  if (include_virsorter2) {
    keep_score[input_seqs$viral>=50] <- keep_score[input_seqs$viral>=50] + 0.5
    keep_score[input_seqs$viral>=95] <- keep_score[input_seqs$viral>=95] + 0.5
  }
  
  if (include_virsorter) {
    keep_score[input_seqs$category==1] <- keep_score[input_seqs$category==1] + 1
    keep_score[input_seqs$category==2] <- keep_score[input_seqs$category==2] + 1
    keep_score[input_seqs$category==3] <- keep_score[input_seqs$category==3] + 0.5
    keep_score[input_seqs$category==4] <- keep_score[input_seqs$category==4] + 1
    keep_score[input_seqs$category==5] <- keep_score[input_seqs$category==5] + 1
    keep_score[input_seqs$category==6] <- keep_score[input_seqs$category==6] + 0.5
  }
  
  if (include_deepvirfinder) {
    keep_score[input_seqs$score>=0.7 & input_seqs$checkv_length<20000] <- keep_score[input_seqs$score>=0.7 & input_seqs$checkv_length<20000] + 0.5
   keep_score[input_seqs$score>=0.9 & input_seqs$checkv_length<20000] <- keep_score[input_seqs$score>=0.9 & input_seqs$checkv_length<20000] + 0.5
  }
  
  if (include_kaiju) {
    keep_score[input_seqs$Kaiju_Viral=="cellular organisms"] <- keep_score[input_seqs$Kaiju_Viral=="cellular organisms"] - 1
    keep_score[input_seqs$Kaiju_Viral=="Viruses"] <- keep_score[input_seqs$Kaiju_Viral=="Viruses"] + 0.5
  }
  
  if (include_tuning) {
    keep_score[input_seqs$hallmark>2] <- keep_score[input_seqs$hallmark>2] + 1
    keep_score[input_seqs$checkv_host_genes>50 & input_seqs$checkv_provirus=="No"] <- keep_score[input_seqs$checkv_host_genes>50 & input_seqs$checkv_provirus=="No"] - 1
    keep_score[input_seqs$percent_unknown>=75 & input_seqs$checkv_length<50000] <- keep_score[input_seqs$percent_unknown>=75 & input_seqs$checkv_length<50000] + 0.5
    keep_score[input_seqs$percent_viral>=50] <- keep_score[input_seqs$percent_viral>=50] + 0.5
    #keep_score[input_seqs$hallmark>=(input_seqs$checkv_viral_genes/5)] <- keep_score[input_seqs$hallmark>=(input_seqs$checkv_viral_genes/5)] + 1 #add some ratio
    keep_score[input_seqs$checkv_viral_genes==0 & input_seqs$checkv_host_genes>=1] <- keep_score[input_seqs$checkv_viral_genes==0 & input_seqs$checkv_host_genes>=1] - 1
    keep_score[((input_seqs$checkv_viral_genes*3) <= input_seqs$checkv_host_genes) & input_seqs$checkv_provirus=="No"] <- keep_score[((input_seqs$checkv_viral_genes*3) <= input_seqs$checkv_host_genes) & input_seqs$checkv_provirus=="No"] - 1 # consider accounting for provirus designation
#    keep_score[(input_seqs$checkv_viral_genes*3) <= input_seqs$checkv_host_genes] <- 0 # consider accounting for provirus designation
    keep_score[input_seqs$checkv_length>500000 & input_seqs$hallmark==0] <- keep_score[input_seqs$checkv_length>500000 & input_seqs$hallmark==0] - 1
  }
  
  return(keep_score)
  
}

Assessing performance against the “truth”

note that this is only as accurate as the annotations of the input sequences

this function calculates the precision, recall, and F1 score for each pipeline

assess_performance <- function(seqtype, keep_score) {
  
  truepositive <- rep("not viral", length(seqtype))
  truepositive[seqtype=="virus"] <- "viral"
  
  #make confusion matrix
  confusion_matrix <- rep("true negative", length(keep_score))
  confusion_matrix[truepositive=="viral" & keep_score<=1] <- "false negative"
  confusion_matrix[truepositive=="viral" & keep_score>=1] <- "true positive"
  confusion_matrix[truepositive=="not viral" & keep_score>=1] <- "false positive"
  
  TP <- table(confusion_matrix)[4]
  FP <- table(confusion_matrix)[2]
  TN <- table(confusion_matrix)[3]
  FN <- table(confusion_matrix)[1]
  
  precision <- TP/(TP+FP)
  recall <- TP/(TP+FN)
  F1 <- 2*precision*recall/(precision+recall)
  
  MCC <- (TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN))
  
  auc <- round(auc(truepositive, keep_score),4)
  
  #by type metrics
  fungal_FP <- table(confusion_matrix[seqtype=="fungi"])[2]
  protist_FP <- table(confusion_matrix[seqtype=="protist"])[2]
  bacterial_FP <- table(confusion_matrix[seqtype=="bacteria"])[2]
  viral_FN <- table(confusion_matrix[seqtype=="virus"])[1]
  
  performance <- c(precision, recall, F1, MCC, auc, fungal_FP, 
                   protist_FP, bacterial_FP, viral_FN)
  names(performance) <- c("precision", "recall", "F1", "MCC", "AUC", "fungal_FP",
                          "protist_FP", "bacterial_FP", "viral_FN")
  
  return(performance)
}

combination of tools list

combos_list <- data.frame(toolcombo=rep(0, 64),
                          CheckV=rep(0, 64),
                          DVF=rep(0, 64),
                          Kaiju=rep(0, 64),
                          VIBRANT=rep(0, 64),
                          VS=rep(0, 64),
                          VS2=rep(0, 64))
p <- 1

for (i in c(0,1)){
  for (j in c(0,1)){
    for (k in c(0,1)){
      for (l in c(0,1)){
        for (m in c(0,1)){
          for (n in c(0,1)){
            combos_list$toolcombo[p] <- paste(i,j,k,l,m,n)
            combos_list$toolcombo2[p] <- paste(if(i){"C"}else{"0"},if(j){"DVF"}else{"0"},
                                               if(k){"K"}else{"0"},if(l){"VB"}else{"0"},
                                               if(m){"VS"}else{"0"},if(n){"VS2"}else{"0"})
            combos_list$CheckV[p] <- i
            combos_list$DVF[p] <- j
            combos_list$Kaiju[p] <- k
            combos_list$VIBRANT[p] <- l
            combos_list$VS[p] <- m
            combos_list$VS2[p] <- n
            p <- p+1
          }
        }
      }
    }
  }
}

combos_list <- combos_list[-1,]

this function builds a list of all of the combinations that the user wants to test. In this case, we’re comparing the performance of all unique combinations of the six tools.

build_score_list <- function(input_seqs, combos) {
Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
2: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
3: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
4: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
5: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
6: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
7: Unknown or uninitialised column: `keep_score_visualize`. 
8: Unknown or uninitialised column: `truepositive`. 
  output <- data.frame(precision=rep(0, nrow(combos)),
                       recall=rep(0, nrow(combos)),
                       F1=rep(0, nrow(combos)),
                       MCC=rep(0, nrow(combos)),
                       AUC=rep(0, nrow(combos)),
                       fungal_FP=rep(0, nrow(combos)),
                       protist_FP=rep(0, nrow(combos)),
                       bacterial_FP=rep(0, nrow(combos)),
                       viral_FN=rep(0, nrow(combos)))
  for (i in 1:nrow(combos)) {
    keep_score <- getting_viral_set_1(input_seqs, include_vibrant = combos$VIBRANT[i],
                                            include_virsorter = combos$VS[i],
                                            include_virsorter2 = combos$VS2[i],
                                            include_tuning = combos$CheckV[i],
                                            include_kaiju = combos$Kaiju[i],
                                            include_deepvirfinder = combos$DVF[i])
  
    output[i,1:9] <- assess_performance(input_seqs$seqtype, keep_score)
    
    output$toolcombo[i] <- paste(combos$CheckV[i],combos$DVF[i],
                                 combos$Kaiju[i], combos$VIBRANT[i],
                                 combos$VS[i], combos$VS2[i])
  }
  
  output[is.na(output)] <- 0

  return (output)
}

Calculate the performance of each pipeline

accuracy_scores <- data.frame(testing_set_index=rep(0, nrow(combos_list)*10),
                      precision=rep(0, nrow(combos_list)*10),
                       recall=rep(0, nrow(combos_list)*10),
                       F1=rep(0, nrow(combos_list)*10),
                       MCC=rep(0, nrow(combos_list)*10), 
                      AUC=rep(0, nrow(combos_list)*10),
                      fungal_FP=rep(0, nrow(combos_list)*10),
                      protist_FP=rep(0, nrow(combos_list)*10),
                      bacterial_FP=rep(0, nrow(combos_list)*10),
                      viral_FN=rep(0, nrow(combos_list)*10))

accuracy_scores <- cbind(testing_set_index=rep(1, nrow(combos_list)),
                              build_score_list(viruses[viruses$Index==1,], combos_list))
for (i in 2:10) {
  accuracy_scores <- rbind(accuracy_scores,
                           cbind(testing_set_index=rep(i, nrow(combos_list)),
                              build_score_list(viruses[viruses$Index==i,], combos_list)))
}
library("stringr")
Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
2: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
3: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
4: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
5: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
6: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
7: Unknown or uninitialised column: `keep_score_visualize`. 
8: Unknown or uninitialised column: `truepositive`. 
accuracy_scores$numtools <- str_count(accuracy_scores$toolcombo, "1")
Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
2: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
3: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
4: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
5: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
6: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
7: Unknown or uninitialised column: `keep_score_visualize`. 
8: Unknown or uninitialised column: `truepositive`. 
#accuracy_scores <- accuracy_scores[order(accuracy_scores$numtools, decreasing=F),]
accuracy_scores <- accuracy_scores[order(accuracy_scores$MCC, decreasing=F),]
accuracy_scores$toolcombo <- factor(accuracy_scores$toolcombo, levels = unique(accuracy_scores$toolcombo))
accuracy_scores$numtools <- as.factor(accuracy_scores$numtools)

Visualize how the precision, recall, and F1 scores change across pipelines.

pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
2: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
3: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
4: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
5: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
6: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
7: Unknown or uninitialised column: `keep_score_visualize`. 
8: Unknown or uninitialised column: `truepositive`. 
p2 <- ggplot(accuracy_scores, aes(x=toolcombo, y=F1, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("F1 Score")
p2

ggplot(accuracy_scores, aes(x=toolcombo, y=precision, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Precision")

ggplot(accuracy_scores, aes(x=toolcombo, y=recall, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Recall")

ggplot(accuracy_scores, aes(x=precision, y=recall, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Precision") +
  ylab("Recall")

ggplot(accuracy_scores, aes(x=toolcombo, y=abs(precision-recall), 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Precision-Recall")

ggplot(accuracy_scores, aes(x=toolcombo, y=MCC, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("MCC")

ggplot(accuracy_scores, aes(x=toolcombo, y=AUC, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("AUC")

ggplot(accuracy_scores, aes(x=toolcombo, y=fungal_FP, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Fungal False Positives")


ggplot(accuracy_scores, aes(x=toolcombo, y=protist_FP, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Protist False Positives")


ggplot(accuracy_scores, aes(x=toolcombo, y=bacterial_FP, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Bacterial False Positives")

ggplot(accuracy_scores, aes(x=toolcombo, y=viral_FN, 
                                  color=numtools, fill=numtools)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Viral False Negatives")

write_tsv(accuracy_scores, "20220927_accuracy_scores.tsv")

to do: add in clustering and ordination like in the drinking water R notebook

Experimenting

high precision example

viruses$keep_score_high_precision <- getting_viral_set_1(viruses, include_deepvirfinder = F,
Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
2: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
3: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
4: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
5: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
6: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
7: Unknown or uninitialised column: `keep_score_visualize`. 
8: Unknown or uninitialised column: `truepositive`. 
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_kaiju = T,
                                              include_tuning = T,
                                              include_virsorter = F)
viruses$confusion_matrix_high_precision <- "true negative"
Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
2: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
3: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
4: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
5: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
6: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
7: Unknown or uninitialised column: `keep_score_visualize`. 
8: Unknown or uninitialised column: `truepositive`. 
viruses$confusion_matrix_high_precision[viruses$seqtype=="virus" & viruses$keep_score_high_precision<1] <- "false negative"
viruses$confusion_matrix_high_precision[viruses$seqtype=="virus" & viruses$keep_score_high_precision>=1] <- "true positive"
viruses$confusion_matrix_high_precision[viruses$seqtype!="virus" & viruses$keep_score_high_precision>=1] <- "false positive"

visualizing confusion matrix by taxa

confusion_by_taxa <- melt(table(viruses$confusion_matrix_high_precision, viruses$seqtype, viruses$Index))
The melt generic in data.table has been passed a table and will attempt to redirect to the relevant reshape2 method; please note that reshape2 is deprecated, and this redirection is now deprecated as well. To continue using melt methods from reshape2 while both libraries are attached, e.g. melt.list, you can prepend the namespace like reshape2::melt(table(viruses$confusion_matrix_high_precision, viruses$seqtype,     viruses$Index)). In the next version, this warning will become an error.
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
2: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
3: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
4: Unknown or uninitialised column: `keep_score_visualize`. 
5: Unknown or uninitialised column: `truepositive`. 
ggplot(confusion_by_taxa, aes(x=count, y=as.factor(Index),
                   fill=confusion_matrix,
                   color=confusion_matrix)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

 ggplot(viruses, aes(x=checkv_viral_genes, y=confusion_matrix_high_precision,
                   fill=confusion_matrix_high_precision,
                   color=confusion_matrix_high_precision)) +
  geom_boxplot(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Viral Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()


 ggplot(viruses, aes(x=percent_viral, y=confusion_matrix_high_precision,
                   fill=confusion_matrix_high_precision,
                   color=confusion_matrix_high_precision)) +
  geom_boxplot(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Percent Genes Viral") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()


 ggplot(viruses, aes(x=hallmark, y=confusion_matrix_high_precision,
                   fill=confusion_matrix_high_precision,
                   color=confusion_matrix_high_precision)) +
  geom_boxplot(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Hallmark Genes") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

 
ggplot(viruses, aes(x=hallmark, y=checkv_viral_genes,
                   fill=confusion_matrix_high_precision,
                   color=confusion_matrix_high_precision)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Hallmark Genes") +
  ylab("Number of Viral Genes") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

viruses_false_positive <- viruses[viruses$confusion_matrix_high_precision=="false positive",]
viruses_false_negative <- viruses[viruses$confusion_matrix_high_precision=="false negative",]
ggplot(viruses, aes(x=hallmark, y=checkv_viral_genes,
                   fill=checkv_length,
                   color=checkv_length,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Number of Viral Genes") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()


ggplot(viruses_false_positive, aes(x=hallmark, y=checkv_length,
                   fill=checkv_viral_genes,
                   color=checkv_viral_genes,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()


ggplot(viruses_false_positive[viruses_false_positive$seqtype=="bacteria"], aes(x=hallmark, y=checkv_length,
                   fill=checkv_viral_genes,
                   color=checkv_viral_genes,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()


ggplot(viruses_false_positive[viruses_false_positive$seqtype=="fungi"], aes(x=hallmark, y=checkv_length,
                   fill=keep_score_high_precision,
                   color=keep_score_high_precision,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()


ggplot(viruses_false_positive[viruses_false_positive$seqtype=="protist"], aes(x=hallmark, y=checkv_length,
                   fill=checkv_viral_genes,
                   color=checkv_viral_genes,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()


ggplot(viruses_false_negative, aes(x=hallmark, y=checkv_length,
                   fill=checkv_viral_genes,
                   color=checkv_viral_genes,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()


ggplot(viruses_false_negative, aes(x=hallmark, y=checkv_length,
                   fill=keep_score_high_precision,
                   color=keep_score_high_precision,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()

table(viruses$hallmark[viruses$confusion_matrix_high_precision=="false positive"]>0)

FALSE  TRUE 
 4398  2608 
table(viruses$percent_host[viruses$confusion_matrix_high_precision=="false positive"]<50)

FALSE  TRUE 
  855  6151 

high MCC example

viruses$keep_score_high_MCC <- getting_viral_set_1(viruses, include_deepvirfinder = F,
There were 41 warnings (use warnings() to see them)
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_kaiju = T,
                                              include_tuning = T,
                                              include_virsorter = T)
viruses$confusion_matrix_high_MCC <- "true negative"
There were 22 warnings (use warnings() to see them)
viruses$confusion_matrix_high_MCC[viruses$seqtype=="virus" & viruses$keep_score_high_MCC<1] <- "false negative"
viruses$confusion_matrix_high_MCC[viruses$seqtype=="virus" & viruses$keep_score_high_MCC>=1] <- "true positive"
viruses$confusion_matrix_high_MCC[viruses$seqtype!="virus" & viruses$keep_score_high_MCC>=1] <- "false positive"

visualizing confusion matrix by taxa

confusion_by_taxa <- melt(table(viruses$confusion_matrix_high_MCC, viruses$seqtype, viruses$Index))
The melt generic in data.table has been passed a table and will attempt to redirect to the relevant reshape2 method; please note that reshape2 is deprecated, and this redirection is now deprecated as well. To continue using melt methods from reshape2 while both libraries are attached, e.g. melt.list, you can prepend the namespace like reshape2::melt(table(viruses$confusion_matrix_high_MCC, viruses$seqtype, viruses$Index)). In the next version, this warning will become an error.Warning messages:
1: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
2: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
3: Unknown or uninitialised column: `confusion_matrix_high_precision`. 
4: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
5: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
6: Unknown or uninitialised column: `confusion_matrix_high_recall`. 
7: Unknown or uninitialised column: `keep_score_visualize`. 
8: Unknown or uninitialised column: `truepositive`. 
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(confusion_by_taxa, aes(x=count, y=as.factor(Index),
                   fill=confusion_matrix,
                   color=confusion_matrix)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

high recall example

viruses$keep_score_high_recall <- getting_viral_set_1(viruses, include_deepvirfinder = T,
There were 15 warnings (use warnings() to see them)
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_kaiju = T,
                                              include_tuning = T,
                                              include_virsorter = T)
viruses$confusion_matrix_high_recall <- "true negative"
viruses$confusion_matrix_high_recall[viruses$seqtype=="virus" & viruses$keep_score_high_recall<1] <- "false negative"
viruses$confusion_matrix_high_recall[viruses$seqtype=="virus" & viruses$keep_score_high_recall>=1] <- "true positive"
viruses$confusion_matrix_high_recall[viruses$seqtype!="virus" & viruses$keep_score_high_recall>=1] <- "false positive"

visualizing confusion matrix by taxa

confusion_by_taxa <- melt(table(viruses$confusion_matrix_high_recall, viruses$seqtype, viruses$Index))
The melt generic in data.table has been passed a table and will attempt to redirect to the relevant reshape2 method; please note that reshape2 is deprecated, and this redirection is now deprecated as well. To continue using melt methods from reshape2 while both libraries are attached, e.g. melt.list, you can prepend the namespace like reshape2::melt(table(viruses$confusion_matrix_high_recall, viruses$seqtype,     viruses$Index)). In the next version, this warning will become an error.
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
Warning messages:
1: Unknown or uninitialised column: `keep_score_visualize`. 
2: Unknown or uninitialised column: `truepositive`. 
p2 <- ggplot(confusion_by_taxa, aes(x=count, y=as.factor(Index),
                   fill=confusion_matrix,
                   color=confusion_matrix)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()
p2

looking at false negatives

viruses_false_negs <- viruses[(viruses$seqtype=="virus" & viruses$keep_score_high_recall<=1),]

Visualizing confusion matrix by number of tools

viruses$keep_score_visualize <- viruses$keep_score
viruses$keep_score_visualize[viruses$keep_score>1] <- ">1"
viruses$keep_score_visualize <- factor(viruses$keep_score_visualize, 
                                       levels=c("-0.5", "-1", "0", "0.5","1", ">1"))
viruses$keep_score_visualize <- factor(viruses$keep_score_visualize, 
                                       labels=c("≤ 0", "≤ 0", "≤ 0", "0.5","1", "> 1"))
levels(factor(viruses$keep_score_visualize))
pal <- ggthemes::tableau_color_pal(palette="Tableau 20", type="regular")
p1 <- ggplot(viruses, aes(x=as.factor(Index),
                   fill=keep_score_visualize, color=keep_score_visualize)) +
  geom_bar(stat="count", position="stack") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16)
  ) +
  scale_color_manual(name = 'Viral Score',
                     values = alpha(c(pal(4)), 1)) +
  scale_fill_manual(name = 'Viral Score',
                     values = alpha(c(pal(4)), 0.5)) +
  xlab("Index") +
  ylab("Sequence Count") +
  facet_wrap(~confusion_matrix, scales = "free")
p1

ROC

library(pROC)
viruses$truepositive <- rep(0, nrow(viruses))
viruses$truepositive[viruses$seqtype=="virus"] <- 1
rocobj <- roc(viruses$truepositive, viruses$keep_score)
rocobj_all <- roc(viruses$truepositive, viruses$keep_score_all)
auc <- round(auc(viruses$truepositive, viruses$keep_score),4)
auc_all <- round(auc(viruses$truepositive, viruses$keep_score_all),4)
#create ROC plot
ggroc(rocobj, colour = 'steelblue', size = 2) +
  ggtitle(paste0('ROC Curve ', '(AUC = ', auc, ')')) +
  coord_equal()
ggroc(rocobj_all, colour = 'green', size = 2) +
  ggtitle(paste0('ROC Curve ', '(AUC = ', auc_all, ')'))

Sensitivity: The probability that the model predicts a positive outcome for an observation when indeed the outcome is positive. Specificity: The probability that the model predicts a negative outcome for an observation when indeed the outcome is negative.

Comparing behavior of all testing sets combined (clustering analyses)

viral_scores <- matrix(data=0, nrow=nrow(viruses), ncol=nrow(combos_list))
num_viruses <- data.frame(toolcombo=rep(0, nrow(combos_list)),
                          num_viruses=rep(0, nrow(combos_list)))

for (i in 1:nrow(combos_list)) {
  viral_scores[,i] <- getting_viral_set_1(viruses, include_vibrant = combos_list$VIBRANT[i],
                                            include_virsorter = combos_list$VS[i],
                                            include_virsorter2 = combos_list$VS2[i],
                                            include_tuning = combos_list$CheckV[i],
                                            include_kaiju = combos_list$Kaiju[i],
                                            include_deepvirfinder = combos_list$DVF[i])
  
  num_viruses$num_viruses[i] <- table(viral_scores[,i]>=1)[[2]]
  
  num_viruses$toolcombo[i] <- combos_list$toolcombo[i]
  
  num_viruses$toolcombo2[i] <- combos_list$toolcombo2[i]
}

num_viruses$numtools <- str_count(num_viruses$toolcombo, "1")
num_viruses <- num_viruses[order(num_viruses$num_viruses, decreasing=F),]
num_viruses$toolcombo <- factor(num_viruses$toolcombo, levels = unique(num_viruses$toolcombo))
num_viruses$toolcombo2 <- factor(num_viruses$toolcombo2, levels = unique(num_viruses$toolcombo2))
num_viruses$numtools <- as.factor(num_viruses$numtools)
ggplot(num_viruses, aes(x=toolcombo, y=num_viruses, 
                                  color=numtools, fill=numtools)) +
  geom_point() +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Num Viruses Predicted")

ggplot(num_viruses, aes(x=toolcombo2, y=num_viruses, 
                                  color=numtools, fill=numtools)) +
  geom_point() +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Num Viruses Predicted")
ggplot(num_viruses, aes(x=numtools, y=num_viruses)) +
  geom_boxplot(aes(color=numtools)) +
  geom_point(aes(color=numtools, fill=numtools)) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Tools") +
  ylab("Num Viruses Predicted")
viral_scores_nozeros <- viral_scores[rowSums(viral_scores)>0,]
viral_scores_nozeros <- viral_scores_nozeros + 1
viral_scores_nozeros <- as.data.frame(viral_scores_nozeros)

colnames(viral_scores_nozeros) <- num_viruses$toolcombo2
tooldata <- num_viruses

rownames(tooldata) <- tooldata$toolcombo2
tooldata <- num_viruses

rownames(tooldata) <- tooldata$toolcombo2
physeq_pooled <- phyloseq(otu_table(viral_scores_nozeros, taxa_are_rows = T),
                                     sample_data(tooldata))
ordination <- phyloseq::ordinate(physeq =physeq_pooled, method = "PCoA", distance = "bray")
phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
                          shape="numtools", color="num_viruses") + 
  geom_point(size = 3) +
  theme_bw() +
  geom_label(label=tooldata$toolcombo)

phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
                          shape="numtools", color="num_viruses") + 
  geom_point(size = 3) +
  theme_bw()

to do: try coloring above based on the F1 scores of the testing set on each combination

bray_dist <- phyloseq::distance(physeq_pooled, method="bray")
clusters <- hclust(dist(bray_dist))
plot(clusters)

myclusters <- cutree(clusters, h=1.1)
names(myclusters[myclusters==1])
names(myclusters[myclusters==2])
names(myclusters[myclusters==3])
names(myclusters[myclusters==4])
names(myclusters[myclusters==5])

myclusters_df <- tibble(combo=names(myclusters),
                            cluster_index=myclusters)

myclusters_df <- separate(myclusters_df, col=combo, into=c("CheckV", "DVF",
                                                            "Kaiju", "VIBRANT",
                                                            "VirSorter", "VirSorter2"),
                          sep=" ", remove = F)


tool_count <- as.data.frame(rbind(table(myclusters_df$CheckV, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$DVF, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$Kaiju, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VIBRANT, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VirSorter, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VirSorter2, myclusters_df$cluster_index)[2,])
                    )

tool_count$method <- c("CheckV", "DVF", "Kaiju", "VIBRANT", "VirSorter", "VirSorter2")

tool_count <- melt(tool_count)

colnames(tool_count) <- c("tool", "cluster_index", "tool_count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")

ggplot(tool_count, aes(x=cluster_index, y=tool_count,
                   fill=cluster_index,
                   color=cluster_index)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) +
  xlab("Cluster") +
  ylab("Number of Times in Cluster") + 
  facet_wrap(~tool, scales = "free")
LS0tCnRpdGxlOiAiVmlyYWwgU2VxdWVuY2UgU29ydGluZyBUb29scyBFdmFsdWF0aW9uIgphdXRob3I6IEJyaWRnZXQgSGVnYXJ0eSwgSmFtZXMgUmlkZGVsbApkYXRlOiAwNy0yMi0yMDIyCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KVGhpcyBSbWFya2Rvd24gZmlsZSBhc3Nlc3NlcyB0aGUgb3V0cHV0IG9mIENoZWNrViwgRGVlcFZpckZpbmRlciwgS2FpanUsClZJQlJBTlQsIFZpclNvcnRlciwgYW5kIFZpclNvcnRlcjIgb24gbXVsdGlwbGUgdHJhaW5pbmcgc2V0cyBvZiBtaWNyb2JpYWwgRE5BLCAKcHJpbWFyaWx5IGZyb20gTkNCSS4gQ3JlYXRlZCBmcm9tIGZ1bmdhbCwgdmlyYWwsIGJhY3RlcmlhbCwgYXJjaGVhZWwsIHByb3Rpc3QsCmFuZCBwbGFzbWlkIEROQSBzZXF1ZW5jZXMKClBsZWFzZSByZWFjaCBvdXQgdG8gSmFtZXMgUmlkZGVsbCAocmlkZGVsbC4yNkBidWNrZXllbWFpbC5vc3UuZWR1KSBvcgpCcmlkZ2V0IEhlZ2FydHkgKGJlaDUzQGNhc2UuZWR1KSByZWdhcmRpbmcgYW55IGlzc3Vlcywgb3Igb3BlbiBhbiBpc3N1ZSBvbiBnaXRodWIuCgpgYGB7ciBzZXR1cC1saWJyYXJ5fQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGx5cikKbGlicmFyeShyZXNoYXBlMikKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkocFJPQykKYGBgCgpJbXBvcnQgdGhlIGZpbGUgdGhhdCBjb21iaW5lcyB0aGUgcmVzdWx0cyBmcm9tIGVhY2ggb2YgdGhlIHRvb2xzIGZyb20gcnVubmluZyAiY29tYmluaW5nX3Rvb2xfb3V0cHV0LlJtZCI6CmBgYHtyfQp2aXJ1c2VzIDwtIHJlYWRfdHN2KCIuLi9JbnRlcm1lZGlhcnlGaWxlcy92aXJhbF90b29sc19jb21iaW5lZC50c3YiKQpgYGAKClRoaXMgc2VjdGlvbiBkZWZpbmVzIGEgdmlyYWxuZXNzIHNjb3JlICJrZWVwX3Njb3JlIiBiYXNlZCBvbiB0aGUgdG9vbCBjbGFzc2lmaWNhdGlvbnMuIApBIGZpbmFsIGtlZXBfc2NvcmUgYWJvdmUgMSBpbmRpY2F0ZXMgd2Ugd2lsbCBrZWVwIHRoYXQgc2VxdWVuY2UgYW5kIGNhbGwgaXQgdmlyYWwuCgpWSUJSQU5UCiAgICBRdWFsaXR5ID09ICJIaWdoIFF1YWxpdHkgRHJhZnQiOiArMQogICAgUXVhbGl0eSA9PSAiTWVkaXVtIFF1YWxpdHkgRHJhZnQiOiArMQogICAgUXVhbGl0eSA9PSAiTG93IFF1YWxpdHkgRHJhZnQiICYgcHJvdmlydXMgPT0gVFJVRTogKzAuNQoKVmlyc29ydGVyMgogICAgVmlyYWwgPj0gNTA6ICswLjUKICAgIFZpcmFsID49IDAuOTU6ICswLjUKClZpcnNvcnRlcgogICAgY2F0ZWdvcnkgPT0gIDEsMiw0LDU6ICsxCiAgICBjYXRlZ29yeSA9PSAzLDY6ICswLjUKCkRlZXBWaXJGaW5kZXI6CiAgICBTY29yZSA+PSAwLjc6ICswLjUKICAgIFNjb3JlID49IDAuOTogKzAuNQoKS2FpanU6CiAgICBLYWlqdV92aXJhbCA9ICJjZWxsdWxhciBvcmdhbmlzbXMiOiAtMQogICAgS2FpanVfdmlyYWwgPSAiVmlydXNlcyI6ICsxCgpDaGVja1YKICAgIElmICV1bmtub3duID49IDc1OiArMC41CiAgICBIYWxsbWFyayA+IDI6ICsxCiAgICB2aXJhbF9nZW5lcyA9PSAwIGFuZCBob3N0X2dlbmVzID49IDE6IGtlZXBfc2NvcmUgPSAwCiAgICBJZiAzKnZpcmFsX2dlbmVzIDw9IGhvc3RfZ2VuZXM6IGtlZXBfc2NvcmUgPSAwCiAgICBJZiBsZW5ndGggPiA1MCwwMDAgYW5kIGhhbGxtYXJrID09IDA6IGtlZXBfc2NvcmUgPSAwCiAgICAKClRoaXMgc2NyaXB0IHByb2R1Y2VzIHZpc3VhbGl6YXRpb25zIG9mIHRoZXNlIGNvbWJpbmVkIHZpcmFsIHNjb3JpbmdzIGFuZAppbmNsdWRlcyBlY29sb2dpY2FsIG1ldHJpY3MgbGlrZSBhbHBoYSBkaXZlcnNpdHkuCgpZb3UgY2FuIGRlY2lkZSB3aGljaCBjb21iaW5hdGlvbiBpcyBhcHByb3ByaWF0ZSBmb3IgdGhlbSBhbmQgb25seSBuZWVkIHVzZSB0aGUKdG9vbHMgYXBwcm9wcmlhdGUgZm9yIHlvdXIgZGF0YS4KCmBgYHtyIGdldHRpbmdfdmlyYWxfc2V0XzF9CmdldHRpbmdfdmlyYWxfc2V0XzEgPC0gZnVuY3Rpb24oaW5wdXRfc2VxcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQ9RkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMj1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX2RlZXB2aXJmaW5kZXI9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmc9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9rYWlqdT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcj1GQUxTRSkgewogIAogIGtlZXBfc2NvcmUgPC0gcmVwKDAsIG5yb3coaW5wdXRfc2VxcykpCiAgCiAgaWYgKGluY2x1ZGVfdmlicmFudCkgewogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpYnJhbnRfcXVhbGl0eT09ImhpZ2ggcXVhbGl0eSBkcmFmdCJdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJoaWdoIHF1YWxpdHkgZHJhZnQiXSArIDEKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJtZWRpdW0gcXVhbGl0eSBkcmFmdCJdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJtZWRpdW0gcXVhbGl0eSBkcmFmdCJdICsgMQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpYnJhbnRfcXVhbGl0eT09ImxvdyBxdWFsaXR5IGRyYWZ0IiAmIGlucHV0X3NlcXMkY2hlY2t2X3Byb3ZpcnVzPT0iWWVzIl0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpYnJhbnRfcXVhbGl0eT09ImxvdyBxdWFsaXR5IGRyYWZ0IiAmIGlucHV0X3NlcXMkY2hlY2t2X3Byb3ZpcnVzPT0iWWVzIl0gKyAwLjUKIyAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlicmFudF9xdWFsaXR5PT0ibG93IHF1YWxpdHkgZHJhZnQiXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlicmFudF9xdWFsaXR5PT0ibG93IHF1YWxpdHkgZHJhZnQiXSArIDAuNQogIH0KICAKICBpZiAoaW5jbHVkZV92aXJzb3J0ZXIyKSB7CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlyYWw+PTUwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlyYWw+PTUwXSArIDAuNQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpcmFsPj05NV0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpcmFsPj05NV0gKyAwLjUKICB9CiAgCiAgaWYgKGluY2x1ZGVfdmlyc29ydGVyKSB7CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2F0ZWdvcnk9PTFdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09MV0gKyAxCiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2F0ZWdvcnk9PTJdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09Ml0gKyAxCiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2F0ZWdvcnk9PTNdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09M10gKyAwLjUKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09NF0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT00XSArIDEKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09NV0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT01XSArIDEKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09Nl0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT02XSArIDAuNQogIH0KICAKICBpZiAoaW5jbHVkZV9kZWVwdmlyZmluZGVyKSB7CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkc2NvcmU+PTAuNyAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDwyMDAwMF0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJHNjb3JlPj0wLjcgJiBpbnB1dF9zZXFzJGNoZWNrdl9sZW5ndGg8MjAwMDBdICsgMC41CiAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRzY29yZT49MC45ICYgaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPDIwMDAwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkc2NvcmU+PTAuOSAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDwyMDAwMF0gKyAwLjUKICB9CiAgCiAgaWYgKGluY2x1ZGVfa2FpanUpIHsKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRLYWlqdV9WaXJhbD09ImNlbGx1bGFyIG9yZ2FuaXNtcyJdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRLYWlqdV9WaXJhbD09ImNlbGx1bGFyIG9yZ2FuaXNtcyJdIC0gMQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJEthaWp1X1ZpcmFsPT0iVmlydXNlcyJdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRLYWlqdV9WaXJhbD09IlZpcnVzZXMiXSArIDAuNQogIH0KICAKICBpZiAoaW5jbHVkZV90dW5pbmcpIHsKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRoYWxsbWFyaz4yXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkaGFsbG1hcms+Ml0gKyAxCiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2hlY2t2X2hvc3RfZ2VuZXM+NTAgJiBpbnB1dF9zZXFzJGNoZWNrdl9wcm92aXJ1cz09Ik5vIl0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNoZWNrdl9ob3N0X2dlbmVzPjUwICYgaW5wdXRfc2VxcyRjaGVja3ZfcHJvdmlydXM9PSJObyJdIC0gMQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJHBlcmNlbnRfdW5rbm93bj49NzUgJiBpbnB1dF9zZXFzJGNoZWNrdl9sZW5ndGg8NTAwMDBdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRwZXJjZW50X3Vua25vd24+PTc1ICYgaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPDUwMDAwXSArIDAuNQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJHBlcmNlbnRfdmlyYWw+PTUwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkcGVyY2VudF92aXJhbD49NTBdICsgMC41CiAgICAja2VlcF9zY29yZVtpbnB1dF9zZXFzJGhhbGxtYXJrPj0oaW5wdXRfc2VxcyRjaGVja3ZfdmlyYWxfZ2VuZXMvNSldIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRoYWxsbWFyaz49KGlucHV0X3NlcXMkY2hlY2t2X3ZpcmFsX2dlbmVzLzUpXSArIDEgI2FkZCBzb21lIHJhdGlvCiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2hlY2t2X3ZpcmFsX2dlbmVzPT0wICYgaW5wdXRfc2VxcyRjaGVja3ZfaG9zdF9nZW5lcz49MV0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNoZWNrdl92aXJhbF9nZW5lcz09MCAmIGlucHV0X3NlcXMkY2hlY2t2X2hvc3RfZ2VuZXM+PTFdIC0gMQogICAga2VlcF9zY29yZVsoKGlucHV0X3NlcXMkY2hlY2t2X3ZpcmFsX2dlbmVzKjMpIDw9IGlucHV0X3NlcXMkY2hlY2t2X2hvc3RfZ2VuZXMpICYgaW5wdXRfc2VxcyRjaGVja3ZfcHJvdmlydXM9PSJObyJdIDwtIGtlZXBfc2NvcmVbKChpbnB1dF9zZXFzJGNoZWNrdl92aXJhbF9nZW5lcyozKSA8PSBpbnB1dF9zZXFzJGNoZWNrdl9ob3N0X2dlbmVzKSAmIGlucHV0X3NlcXMkY2hlY2t2X3Byb3ZpcnVzPT0iTm8iXSAtIDEgIyBjb25zaWRlciBhY2NvdW50aW5nIGZvciBwcm92aXJ1cyBkZXNpZ25hdGlvbgojICAgIGtlZXBfc2NvcmVbKGlucHV0X3NlcXMkY2hlY2t2X3ZpcmFsX2dlbmVzKjMpIDw9IGlucHV0X3NlcXMkY2hlY2t2X2hvc3RfZ2VuZXNdIDwtIDAgIyBjb25zaWRlciBhY2NvdW50aW5nIGZvciBwcm92aXJ1cyBkZXNpZ25hdGlvbgogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNoZWNrdl9sZW5ndGg+NTAwMDAwICYgaW5wdXRfc2VxcyRoYWxsbWFyaz09MF0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNoZWNrdl9sZW5ndGg+NTAwMDAwICYgaW5wdXRfc2VxcyRoYWxsbWFyaz09MF0gLSAxCiAgfQogIAogIHJldHVybihrZWVwX3Njb3JlKQogIAp9CmBgYAoKCgojIEFzc2Vzc2luZyBwZXJmb3JtYW5jZSBhZ2FpbnN0IHRoZSAidHJ1dGgiCm5vdGUgdGhhdCB0aGlzIGlzIG9ubHkgYXMgYWNjdXJhdGUgYXMgdGhlIGFubm90YXRpb25zIG9mIHRoZSBpbnB1dCBzZXF1ZW5jZXMKCnRoaXMgZnVuY3Rpb24gY2FsY3VsYXRlcyB0aGUgcHJlY2lzaW9uLCByZWNhbGwsIGFuZCBGMSBzY29yZSBmb3IgZWFjaCBwaXBlbGluZQpgYGB7cn0KYXNzZXNzX3BlcmZvcm1hbmNlIDwtIGZ1bmN0aW9uKHNlcXR5cGUsIGtlZXBfc2NvcmUpIHsKICAKICB0cnVlcG9zaXRpdmUgPC0gcmVwKCJub3QgdmlyYWwiLCBsZW5ndGgoc2VxdHlwZSkpCiAgdHJ1ZXBvc2l0aXZlW3NlcXR5cGU9PSJ2aXJ1cyJdIDwtICJ2aXJhbCIKICAKICAjbWFrZSBjb25mdXNpb24gbWF0cml4CiAgY29uZnVzaW9uX21hdHJpeCA8LSByZXAoInRydWUgbmVnYXRpdmUiLCBsZW5ndGgoa2VlcF9zY29yZSkpCiAgY29uZnVzaW9uX21hdHJpeFt0cnVlcG9zaXRpdmU9PSJ2aXJhbCIgJiBrZWVwX3Njb3JlPD0xXSA8LSAiZmFsc2UgbmVnYXRpdmUiCiAgY29uZnVzaW9uX21hdHJpeFt0cnVlcG9zaXRpdmU9PSJ2aXJhbCIgJiBrZWVwX3Njb3JlPj0xXSA8LSAidHJ1ZSBwb3NpdGl2ZSIKICBjb25mdXNpb25fbWF0cml4W3RydWVwb3NpdGl2ZT09Im5vdCB2aXJhbCIgJiBrZWVwX3Njb3JlPj0xXSA8LSAiZmFsc2UgcG9zaXRpdmUiCiAgCiAgVFAgPC0gdGFibGUoY29uZnVzaW9uX21hdHJpeClbNF0KICBGUCA8LSB0YWJsZShjb25mdXNpb25fbWF0cml4KVsyXQogIFROIDwtIHRhYmxlKGNvbmZ1c2lvbl9tYXRyaXgpWzNdCiAgRk4gPC0gdGFibGUoY29uZnVzaW9uX21hdHJpeClbMV0KICAKICBwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQogIHJlY2FsbCA8LSBUUC8oVFArRk4pCiAgRjEgPC0gMipwcmVjaXNpb24qcmVjYWxsLyhwcmVjaXNpb24rcmVjYWxsKQogIAogIE1DQyA8LSAoVFAqVE4tRlAqRk4pL3NxcnQoYXMubnVtZXJpYyhUUCtGUCkqYXMubnVtZXJpYyhUUCtGTikqYXMubnVtZXJpYyhUTitGUCkqYXMubnVtZXJpYyhUTitGTikpCiAgCiAgYXVjIDwtIHJvdW5kKGF1Yyh0cnVlcG9zaXRpdmUsIGtlZXBfc2NvcmUpLDQpCiAgCiAgI2J5IHR5cGUgbWV0cmljcwogIGZ1bmdhbF9GUCA8LSB0YWJsZShjb25mdXNpb25fbWF0cml4W3NlcXR5cGU9PSJmdW5naSJdKVsyXQogIHByb3Rpc3RfRlAgPC0gdGFibGUoY29uZnVzaW9uX21hdHJpeFtzZXF0eXBlPT0icHJvdGlzdCJdKVsyXQogIGJhY3RlcmlhbF9GUCA8LSB0YWJsZShjb25mdXNpb25fbWF0cml4W3NlcXR5cGU9PSJiYWN0ZXJpYSJdKVsyXQogIHZpcmFsX0ZOIDwtIHRhYmxlKGNvbmZ1c2lvbl9tYXRyaXhbc2VxdHlwZT09InZpcnVzIl0pWzFdCiAgCiAgcGVyZm9ybWFuY2UgPC0gYyhwcmVjaXNpb24sIHJlY2FsbCwgRjEsIE1DQywgYXVjLCBmdW5nYWxfRlAsIAogICAgICAgICAgICAgICAgICAgcHJvdGlzdF9GUCwgYmFjdGVyaWFsX0ZQLCB2aXJhbF9GTikKICBuYW1lcyhwZXJmb3JtYW5jZSkgPC0gYygicHJlY2lzaW9uIiwgInJlY2FsbCIsICJGMSIsICJNQ0MiLCAiQVVDIiwgImZ1bmdhbF9GUCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgInByb3Rpc3RfRlAiLCAiYmFjdGVyaWFsX0ZQIiwgInZpcmFsX0ZOIikKICAKICByZXR1cm4ocGVyZm9ybWFuY2UpCn0KYGBgCgpjb21iaW5hdGlvbiBvZiB0b29scyBsaXN0CmBgYHtyfQpjb21ib3NfbGlzdCA8LSBkYXRhLmZyYW1lKHRvb2xjb21ibz1yZXAoMCwgNjQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIENoZWNrVj1yZXAoMCwgNjQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIERWRj1yZXAoMCwgNjQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIEthaWp1PXJlcCgwLCA2NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgVklCUkFOVD1yZXAoMCwgNjQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIFZTPXJlcCgwLCA2NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgVlMyPXJlcCgwLCA2NCkpCnAgPC0gMQoKZm9yIChpIGluIGMoMCwxKSl7CiAgZm9yIChqIGluIGMoMCwxKSl7CiAgICBmb3IgKGsgaW4gYygwLDEpKXsKICAgICAgZm9yIChsIGluIGMoMCwxKSl7CiAgICAgICAgZm9yIChtIGluIGMoMCwxKSl7CiAgICAgICAgICBmb3IgKG4gaW4gYygwLDEpKXsKICAgICAgICAgICAgY29tYm9zX2xpc3QkdG9vbGNvbWJvW3BdIDwtIHBhc3RlKGksaixrLGwsbSxuKQogICAgICAgICAgICBjb21ib3NfbGlzdCR0b29sY29tYm8yW3BdIDwtIHBhc3RlKGlmKGkpeyJDIn1lbHNleyIwIn0saWYoail7IkRWRiJ9ZWxzZXsiMCJ9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmKGspeyJLIn1lbHNleyIwIn0saWYobCl7IlZCIn1lbHNleyIwIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYobSl7IlZTIn1lbHNleyIwIn0saWYobil7IlZTMiJ9ZWxzZXsiMCJ9KQogICAgICAgICAgICBjb21ib3NfbGlzdCRDaGVja1ZbcF0gPC0gaQogICAgICAgICAgICBjb21ib3NfbGlzdCREVkZbcF0gPC0gagogICAgICAgICAgICBjb21ib3NfbGlzdCRLYWlqdVtwXSA8LSBrCiAgICAgICAgICAgIGNvbWJvc19saXN0JFZJQlJBTlRbcF0gPC0gbAogICAgICAgICAgICBjb21ib3NfbGlzdCRWU1twXSA8LSBtCiAgICAgICAgICAgIGNvbWJvc19saXN0JFZTMltwXSA8LSBuCiAgICAgICAgICAgIHAgPC0gcCsxCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9CiAgfQp9Cgpjb21ib3NfbGlzdCA8LSBjb21ib3NfbGlzdFstMSxdCmBgYAoKdGhpcyBmdW5jdGlvbiBidWlsZHMgYSBsaXN0IG9mIGFsbCBvZiB0aGUgY29tYmluYXRpb25zIHRoYXQgdGhlIHVzZXIgd2FudHMgdG8gCnRlc3QuIApJbiB0aGlzIGNhc2UsIHdlJ3JlIGNvbXBhcmluZyB0aGUgcGVyZm9ybWFuY2Ugb2YgYWxsIHVuaXF1ZSBjb21iaW5hdGlvbnMgb2YgdGhlIApzaXggdG9vbHMuCmBgYHtyfQpidWlsZF9zY29yZV9saXN0IDwtIGZ1bmN0aW9uKGlucHV0X3NlcXMsIGNvbWJvcykgewogIG91dHB1dCA8LSBkYXRhLmZyYW1lKHByZWNpc2lvbj1yZXAoMCwgbnJvdyhjb21ib3MpKSwKICAgICAgICAgICAgICAgICAgICAgICByZWNhbGw9cmVwKDAsIG5yb3coY29tYm9zKSksCiAgICAgICAgICAgICAgICAgICAgICAgRjE9cmVwKDAsIG5yb3coY29tYm9zKSksCiAgICAgICAgICAgICAgICAgICAgICAgTUNDPXJlcCgwLCBucm93KGNvbWJvcykpLAogICAgICAgICAgICAgICAgICAgICAgIEFVQz1yZXAoMCwgbnJvdyhjb21ib3MpKSwKICAgICAgICAgICAgICAgICAgICAgICBmdW5nYWxfRlA9cmVwKDAsIG5yb3coY29tYm9zKSksCiAgICAgICAgICAgICAgICAgICAgICAgcHJvdGlzdF9GUD1yZXAoMCwgbnJvdyhjb21ib3MpKSwKICAgICAgICAgICAgICAgICAgICAgICBiYWN0ZXJpYWxfRlA9cmVwKDAsIG5yb3coY29tYm9zKSksCiAgICAgICAgICAgICAgICAgICAgICAgdmlyYWxfRk49cmVwKDAsIG5yb3coY29tYm9zKSkpCiAgZm9yIChpIGluIDE6bnJvdyhjb21ib3MpKSB7CiAgICBrZWVwX3Njb3JlIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEoaW5wdXRfc2VxcywgaW5jbHVkZV92aWJyYW50ID0gY29tYm9zJFZJQlJBTlRbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBjb21ib3MkVlNbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gY29tYm9zJFZTMltpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZyA9IGNvbWJvcyRDaGVja1ZbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9rYWlqdSA9IGNvbWJvcyRLYWlqdVtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBjb21ib3MkRFZGW2ldKQogIAogICAgb3V0cHV0W2ksMTo5XSA8LSBhc3Nlc3NfcGVyZm9ybWFuY2UoaW5wdXRfc2VxcyRzZXF0eXBlLCBrZWVwX3Njb3JlKQogICAgCiAgICBvdXRwdXQkdG9vbGNvbWJvW2ldIDwtIHBhc3RlKGNvbWJvcyRDaGVja1ZbaV0sY29tYm9zJERWRltpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tYm9zJEthaWp1W2ldLCBjb21ib3MkVklCUkFOVFtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29tYm9zJFZTW2ldLCBjb21ib3MkVlMyW2ldKQogIH0KICAKICBvdXRwdXRbaXMubmEob3V0cHV0KV0gPC0gMAoKICByZXR1cm4gKG91dHB1dCkKfQpgYGAKCiMjIENhbGN1bGF0ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgZWFjaCBwaXBlbGluZQpgYGB7cn0KYWNjdXJhY3lfc2NvcmVzIDwtIGRhdGEuZnJhbWUodGVzdGluZ19zZXRfaW5kZXg9cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSwKICAgICAgICAgICAgICAgICAgICAgIHByZWNpc2lvbj1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApLAogICAgICAgICAgICAgICAgICAgICAgIHJlY2FsbD1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApLAogICAgICAgICAgICAgICAgICAgICAgIEYxPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSoxMCksCiAgICAgICAgICAgICAgICAgICAgICAgTUNDPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSoxMCksIAogICAgICAgICAgICAgICAgICAgICAgQVVDPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSoxMCksCiAgICAgICAgICAgICAgICAgICAgICBmdW5nYWxfRlA9cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSwKICAgICAgICAgICAgICAgICAgICAgIHByb3Rpc3RfRlA9cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSwKICAgICAgICAgICAgICAgICAgICAgIGJhY3RlcmlhbF9GUD1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApLAogICAgICAgICAgICAgICAgICAgICAgdmlyYWxfRk49cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSkKCmFjY3VyYWN5X3Njb3JlcyA8LSBjYmluZCh0ZXN0aW5nX3NldF9pbmRleD1yZXAoMSwgbnJvdyhjb21ib3NfbGlzdCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidWlsZF9zY29yZV9saXN0KHZpcnVzZXNbdmlydXNlcyRJbmRleD09MSxdLCBjb21ib3NfbGlzdCkpCmZvciAoaSBpbiAyOjEwKSB7CiAgYWNjdXJhY3lfc2NvcmVzIDwtIHJiaW5kKGFjY3VyYWN5X3Njb3JlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgY2JpbmQodGVzdGluZ19zZXRfaW5kZXg9cmVwKGksIG5yb3coY29tYm9zX2xpc3QpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnVpbGRfc2NvcmVfbGlzdCh2aXJ1c2VzW3ZpcnVzZXMkSW5kZXg9PWksXSwgY29tYm9zX2xpc3QpKSkKfQpgYGAKCmBgYHtyfQpsaWJyYXJ5KCJzdHJpbmdyIikKYGBgCgpgYGB7cn0KYWNjdXJhY3lfc2NvcmVzJG51bXRvb2xzIDwtIHN0cl9jb3VudChhY2N1cmFjeV9zY29yZXMkdG9vbGNvbWJvLCAiMSIpCiNhY2N1cmFjeV9zY29yZXMgPC0gYWNjdXJhY3lfc2NvcmVzW29yZGVyKGFjY3VyYWN5X3Njb3JlcyRudW10b29scywgZGVjcmVhc2luZz1GKSxdCmFjY3VyYWN5X3Njb3JlcyA8LSBhY2N1cmFjeV9zY29yZXNbb3JkZXIoYWNjdXJhY3lfc2NvcmVzJE1DQywgZGVjcmVhc2luZz1GKSxdCmFjY3VyYWN5X3Njb3JlcyR0b29sY29tYm8gPC0gZmFjdG9yKGFjY3VyYWN5X3Njb3JlcyR0b29sY29tYm8sIGxldmVscyA9IHVuaXF1ZShhY2N1cmFjeV9zY29yZXMkdG9vbGNvbWJvKSkKYWNjdXJhY3lfc2NvcmVzJG51bXRvb2xzIDwtIGFzLmZhY3RvcihhY2N1cmFjeV9zY29yZXMkbnVtdG9vbHMpCmBgYAoKCiMjIFZpc3VhbGl6ZSBob3cgdGhlIHByZWNpc2lvbiwgcmVjYWxsLCBhbmQgRjEgc2NvcmVzIGNoYW5nZSBhY3Jvc3MgcGlwZWxpbmVzLgpgYGB7cn0KcGFsIDwtIGdndGhlbWVzOjp0YWJsZWF1X2NvbG9yX3BhbChwYWxldHRlPSJUYWJsZWF1IDEwIiwgdHlwZT0icmVndWxhciIpCnAyIDwtIGdncGxvdChhY2N1cmFjeV9zY29yZXMsIGFlcyh4PXRvb2xjb21ibywgeT1GMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW10b29scywgZmlsbD1udW10b29scykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uIChDViwgRFZGLCBLSiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIkYxIFNjb3JlIikKcDIKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9dG9vbGNvbWJvLCB5PXByZWNpc2lvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW10b29scywgZmlsbD1udW10b29scykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uIChDViwgRFZGLCBLSiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIlByZWNpc2lvbiIpCmdncGxvdChhY2N1cmFjeV9zY29yZXMsIGFlcyh4PXRvb2xjb21ibywgeT1yZWNhbGwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtdG9vbHMsIGZpbGw9bnVtdG9vbHMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAoQ1YsIERWRiwgS0osIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJSZWNhbGwiKQpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD1wcmVjaXNpb24sIHk9cmVjYWxsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXRvb2xzLCBmaWxsPW51bXRvb2xzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlByZWNpc2lvbiIpICsKICB5bGFiKCJSZWNhbGwiKQpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9YWJzKHByZWNpc2lvbi1yZWNhbGwpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXRvb2xzLCBmaWxsPW51bXRvb2xzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKENWLCBEVkYsIEtKLCBWQiwgVlMsIFZTMikiKSArCiAgeWxhYigiUHJlY2lzaW9uLVJlY2FsbCIpCmdncGxvdChhY2N1cmFjeV9zY29yZXMsIGFlcyh4PXRvb2xjb21ibywgeT1NQ0MsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtdG9vbHMsIGZpbGw9bnVtdG9vbHMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAoQ1YsIERWRiwgS0osIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJNQ0MiKQpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9QVVDLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXRvb2xzLCBmaWxsPW51bXRvb2xzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKENWLCBEVkYsIEtKLCBWQiwgVlMsIFZTMikiKSArCiAgeWxhYigiQVVDIikKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9dG9vbGNvbWJvLCB5PWZ1bmdhbF9GUCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW10b29scywgZmlsbD1udW10b29scykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uIChDViwgRFZGLCBLSiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIkZ1bmdhbCBGYWxzZSBQb3NpdGl2ZXMiKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9dG9vbGNvbWJvLCB5PXByb3Rpc3RfRlAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtdG9vbHMsIGZpbGw9bnVtdG9vbHMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAoQ1YsIERWRiwgS0osIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJQcm90aXN0IEZhbHNlIFBvc2l0aXZlcyIpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9YmFjdGVyaWFsX0ZQLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXRvb2xzLCBmaWxsPW51bXRvb2xzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKENWLCBEVkYsIEtKLCBWQiwgVlMsIFZTMikiKSArCiAgeWxhYigiQmFjdGVyaWFsIEZhbHNlIFBvc2l0aXZlcyIpCmdncGxvdChhY2N1cmFjeV9zY29yZXMsIGFlcyh4PXRvb2xjb21ibywgeT12aXJhbF9GTiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW10b29scywgZmlsbD1udW10b29scykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uIChDViwgRFZGLCBLSiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIlZpcmFsIEZhbHNlIE5lZ2F0aXZlcyIpCmBgYAoKYGBge3J9CndyaXRlX3RzdihhY2N1cmFjeV9zY29yZXMsICIyMDIyMDkyN19hY2N1cmFjeV9zY29yZXMudHN2IikKYGBgCgp0byBkbzogYWRkIGluIGNsdXN0ZXJpbmcgYW5kIG9yZGluYXRpb24gbGlrZSBpbiB0aGUgZHJpbmtpbmcgd2F0ZXIgUiBub3RlYm9vawoKIyBFeHBlcmltZW50aW5nCgojIyBoaWdoIHByZWNpc2lvbiBleGFtcGxlCmBgYHtyfQp2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9wcmVjaXNpb24gPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX2thaWp1ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKYGBgCgoKYGBge3J9CnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiA8LSAidHJ1ZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbjwxXSA8LSAiZmFsc2UgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvblt2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9wcmVjaXNpb24+PTFdIDwtICJ0cnVlIHBvc2l0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb25bdmlydXNlcyRzZXF0eXBlIT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfcHJlY2lzaW9uPj0xXSA8LSAiZmFsc2UgcG9zaXRpdmUiCmBgYAoKdmlzdWFsaXppbmcgY29uZnVzaW9uIG1hdHJpeCBieSB0YXhhCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YSA8LSBtZWx0KHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwgdmlydXNlcyRzZXF0eXBlLCB2aXJ1c2VzJEluZGV4KSkKY29sbmFtZXMoY29uZnVzaW9uX2J5X3RheGEpIDwtIGMoImNvbmZ1c2lvbl9tYXRyaXgiLCAic2VxdHlwZSIsIkluZGV4IiwgImNvdW50IikKYGBgCgoKCmBgYHtyfQpwYWwgPC0gZ2d0aGVtZXM6OnRhYmxlYXVfY29sb3JfcGFsKHBhbGV0dGU9IlRhYmxlYXUgMTAiLCB0eXBlPSJyZWd1bGFyIikKYGBgCgpgYGB7cn0KZ2dwbG90KGNvbmZ1c2lvbl9ieV90YXhhLCBhZXMoeD1jb3VudCwgeT1hcy5mYWN0b3IoSW5kZXgpLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4LAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeCkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKwogIHlsYWIoIiIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKYGBge3J9CiBnZ3Bsb3QodmlydXNlcywgYWVzKHg9Y2hlY2t2X3ZpcmFsX2dlbmVzLCB5PWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uKSkgKwogIGdlb21fYm94cGxvdChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJOdW1iZXIgb2YgVmlyYWwgU2VxdWVuY2VzIikgKwogIHlsYWIoIiIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgogZ2dwbG90KHZpcnVzZXMsIGFlcyh4PXBlcmNlbnRfdmlyYWwsIHk9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24pKSArCiAgZ2VvbV9ib3hwbG90KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIlBlcmNlbnQgR2VuZXMgVmlyYWwiKSArCiAgeWxhYigiIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCiBnZ3Bsb3QodmlydXNlcywgYWVzKHg9aGFsbG1hcmssIHk9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24pKSArCiAgZ2VvbV9ib3hwbG90KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCIiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQogCmdncGxvdCh2aXJ1c2VzLCBhZXMoeD1oYWxsbWFyaywgeT1jaGVja3ZfdmlyYWxfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIk51bWJlciBvZiBWaXJhbCBHZW5lcyIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKYGBge3J9CnZpcnVzZXNfZmFsc2VfcG9zaXRpdmUgPC0gdmlydXNlc1t2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb249PSJmYWxzZSBwb3NpdGl2ZSIsXQp2aXJ1c2VzX2ZhbHNlX25lZ2F0aXZlIDwtIHZpcnVzZXNbdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uPT0iZmFsc2UgbmVnYXRpdmUiLF0KYGBgCgpgYGB7cn0KZ2dwbG90KHZpcnVzZXMsIGFlcyh4PWhhbGxtYXJrLCB5PWNoZWNrdl92aXJhbF9nZW5lcywKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNoZWNrdl9sZW5ndGgsCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIk51bWJlciBvZiBWaXJhbCBHZW5lcyIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgpnZ3Bsb3QodmlydXNlc19mYWxzZV9wb3NpdGl2ZSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgc2hhcGU9Y2hlY2t2X3Byb3ZpcnVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCJDb250aWcgTGVuZ3RoIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCmdncGxvdCh2aXJ1c2VzX2ZhbHNlX3Bvc2l0aXZlW3ZpcnVzZXNfZmFsc2VfcG9zaXRpdmUkc2VxdHlwZT09ImJhY3RlcmlhIl0sIGFlcyh4PWhhbGxtYXJrLCB5PWNoZWNrdl9sZW5ndGgsCiAgICAgICAgICAgICAgICAgICBmaWxsPWNoZWNrdl92aXJhbF9nZW5lcywKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNoZWNrdl92aXJhbF9nZW5lcywKICAgICAgICAgICAgICAgICAgIHNoYXBlPWNoZWNrdl9wcm92aXJ1cykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJOdW1iZXIgb2YgSGFsbG1hcmsgR2VuZXMiKSArCiAgeWxhYigiQ29udGlnIExlbmd0aCIpICsgCiAgZmFjZXRfd3JhcCh+S2FpanVfVmlyYWwsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQoKZ2dwbG90KHZpcnVzZXNfZmFsc2VfcG9zaXRpdmVbdmlydXNlc19mYWxzZV9wb3NpdGl2ZSRzZXF0eXBlPT0iZnVuZ2kiXSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9a2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWtlZXBfc2NvcmVfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIkNvbnRpZyBMZW5ndGgiKSArIAogIGZhY2V0X3dyYXAofkthaWp1X1ZpcmFsLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCmdncGxvdCh2aXJ1c2VzX2ZhbHNlX3Bvc2l0aXZlW3ZpcnVzZXNfZmFsc2VfcG9zaXRpdmUkc2VxdHlwZT09InByb3Rpc3QiXSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgc2hhcGU9Y2hlY2t2X3Byb3ZpcnVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCJDb250aWcgTGVuZ3RoIikgKyAKICBmYWNldF93cmFwKH5LYWlqdV9WaXJhbCwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgpnZ3Bsb3QodmlydXNlc19mYWxzZV9uZWdhdGl2ZSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgc2hhcGU9Y2hlY2t2X3Byb3ZpcnVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCJDb250aWcgTGVuZ3RoIikgKyAKICBmYWNldF93cmFwKH5LYWlqdV9WaXJhbCwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgpnZ3Bsb3QodmlydXNlc19mYWxzZV9uZWdhdGl2ZSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9a2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWtlZXBfc2NvcmVfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIkNvbnRpZyBMZW5ndGgiKSArIAogIGZhY2V0X3dyYXAofkthaWp1X1ZpcmFsLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgoKCgpgYGB7cn0KdGFibGUodmlydXNlcyRoYWxsbWFya1t2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb249PSJmYWxzZSBwb3NpdGl2ZSJdPjApCgp0YWJsZSh2aXJ1c2VzJHBlcmNlbnRfaG9zdFt2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb249PSJmYWxzZSBwb3NpdGl2ZSJdPDUwKQpgYGAKCiMjIGhpZ2ggTUNDIGV4YW1wbGUKYGBge3J9CnZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQyA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfa2FpanUgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmcgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBUKQpgYGAKCgpgYGB7cn0KdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDIDwtICJ0cnVlIG5lZ2F0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0NbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDPDFdIDwtICJmYWxzZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQz49MV0gPC0gInRydWUgcG9zaXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQ1t2aXJ1c2VzJHNlcXR5cGUhPSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9NQ0M+PTFdIDwtICJmYWxzZSBwb3NpdGl2ZSIKYGBgCgp2aXN1YWxpemluZyBjb25mdXNpb24gbWF0cml4IGJ5IHRheGEKYGBge3J9CmNvbmZ1c2lvbl9ieV90YXhhIDwtIG1lbHQodGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDLCB2aXJ1c2VzJHNlcXR5cGUsIHZpcnVzZXMkSW5kZXgpKQpjb2xuYW1lcyhjb25mdXNpb25fYnlfdGF4YSkgPC0gYygiY29uZnVzaW9uX21hdHJpeCIsICJzZXF0eXBlIiwiSW5kZXgiLCAiY291bnQiKQpgYGAKCgoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29uZnVzaW9uX2J5X3RheGEsIGFlcyh4PWNvdW50LCB5PWFzLmZhY3RvcihJbmRleCksCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXgsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4KSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIk51bWJlciBvZiBTZXF1ZW5jZXMiKSArCiAgeWxhYigiIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgojIyBoaWdoIHJlY2FsbCBleGFtcGxlCmBgYHtyfQp2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9yZWNhbGwgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX2thaWp1ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gVCkKYGBgCgoKYGBge3J9CnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCA8LSAidHJ1ZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3JlY2FsbDwxXSA8LSAiZmFsc2UgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbFt2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9yZWNhbGw+PTFdIDwtICJ0cnVlIHBvc2l0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGxbdmlydXNlcyRzZXF0eXBlIT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfcmVjYWxsPj0xXSA8LSAiZmFsc2UgcG9zaXRpdmUiCmBgYAoKdmlzdWFsaXppbmcgY29uZnVzaW9uIG1hdHJpeCBieSB0YXhhCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YSA8LSBtZWx0KHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCwgdmlydXNlcyRzZXF0eXBlLCB2aXJ1c2VzJEluZGV4KSkKY29sbmFtZXMoY29uZnVzaW9uX2J5X3RheGEpIDwtIGMoImNvbmZ1c2lvbl9tYXRyaXgiLCAic2VxdHlwZSIsIkluZGV4IiwgImNvdW50IikKYGBgCgoKCmBgYHtyfQpwYWwgPC0gZ2d0aGVtZXM6OnRhYmxlYXVfY29sb3JfcGFsKHBhbGV0dGU9IlRhYmxlYXUgMTAiLCB0eXBlPSJyZWd1bGFyIikKYGBgCgpgYGB7cn0KcDIgPC0gZ2dwbG90KGNvbmZ1c2lvbl9ieV90YXhhLCBhZXMoeD1jb3VudCwgeT1hcy5mYWN0b3IoSW5kZXgpLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4LAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeCkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKwogIHlsYWIoIiIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCnAyCmBgYAoKbG9va2luZyBhdCBmYWxzZSBuZWdhdGl2ZXMKYGBge3J9CnZpcnVzZXNfZmFsc2VfbmVncyA8LSB2aXJ1c2VzWyh2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9yZWNhbGw8PTEpLF0KYGBgCgoKCgojIFZpc3VhbGl6aW5nIGNvbmZ1c2lvbiBtYXRyaXggYnkgbnVtYmVyIG9mIHRvb2xzCgoKYGBge3J9CnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemUgPC0gdmlydXNlcyRrZWVwX3Njb3JlCnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemVbdmlydXNlcyRrZWVwX3Njb3JlPjFdIDwtICI+MSIKdmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSA8LSBmYWN0b3IodmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCItMC41IiwgIi0xIiwgIjAiLCAiMC41IiwiMSIsICI+MSIpKQp2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplIDwtIGZhY3Rvcih2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIuKJpCAwIiwgIuKJpCAwIiwgIuKJpCAwIiwgIjAuNSIsIjEiLCAiPiAxIikpCmBgYAoKYGBge3J9CmxldmVscyhmYWN0b3IodmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSkpCmBgYAoKCmBgYHtyfQpwYWwgPC0gZ2d0aGVtZXM6OnRhYmxlYXVfY29sb3JfcGFsKHBhbGV0dGU9IlRhYmxlYXUgMjAiLCB0eXBlPSJyZWd1bGFyIikKcDEgPC0gZ2dwbG90KHZpcnVzZXMsIGFlcyh4PWFzLmZhY3RvcihJbmRleCksCiAgICAgICAgICAgICAgICAgICBmaWxsPWtlZXBfc2NvcmVfdmlzdWFsaXplLCBjb2xvcj1rZWVwX3Njb3JlX3Zpc3VhbGl6ZSkpICsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHBvc2l0aW9uPSJzdGFjayIpICsKICB0aGVtZV9saWdodCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KQogICkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gJ1ZpcmFsIFNjb3JlJywKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEoYyhwYWwoNCkpLCAxKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAnVmlyYWwgU2NvcmUnLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShjKHBhbCg0KSksIDAuNSkpICsKICB4bGFiKCJJbmRleCIpICsKICB5bGFiKCJTZXF1ZW5jZSBDb3VudCIpICsKICBmYWNldF93cmFwKH5jb25mdXNpb25fbWF0cml4LCBzY2FsZXMgPSAiZnJlZSIpCnAxCmBgYAoKCiMgUk9DIApgYGB7cn0KbGlicmFyeShwUk9DKQpgYGAKCmBgYHtyfQp2aXJ1c2VzJHRydWVwb3NpdGl2ZSA8LSByZXAoMCwgbnJvdyh2aXJ1c2VzKSkKdmlydXNlcyR0cnVlcG9zaXRpdmVbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiXSA8LSAxCmBgYAoKCmBgYHtyfQpyb2NvYmogPC0gcm9jKHZpcnVzZXMkdHJ1ZXBvc2l0aXZlLCB2aXJ1c2VzJGtlZXBfc2NvcmUpCnJvY29ial9hbGwgPC0gcm9jKHZpcnVzZXMkdHJ1ZXBvc2l0aXZlLCB2aXJ1c2VzJGtlZXBfc2NvcmVfYWxsKQphdWMgPC0gcm91bmQoYXVjKHZpcnVzZXMkdHJ1ZXBvc2l0aXZlLCB2aXJ1c2VzJGtlZXBfc2NvcmUpLDQpCmF1Y19hbGwgPC0gcm91bmQoYXVjKHZpcnVzZXMkdHJ1ZXBvc2l0aXZlLCB2aXJ1c2VzJGtlZXBfc2NvcmVfYWxsKSw0KQojY3JlYXRlIFJPQyBwbG90Cmdncm9jKHJvY29iaiwgY29sb3VyID0gJ3N0ZWVsYmx1ZScsIHNpemUgPSAyKSArCiAgZ2d0aXRsZShwYXN0ZTAoJ1JPQyBDdXJ2ZSAnLCAnKEFVQyA9ICcsIGF1YywgJyknKSkgKwogIGNvb3JkX2VxdWFsKCkKZ2dyb2Mocm9jb2JqX2FsbCwgY29sb3VyID0gJ2dyZWVuJywgc2l6ZSA9IDIpICsKICBnZ3RpdGxlKHBhc3RlMCgnUk9DIEN1cnZlICcsICcoQVVDID0gJywgYXVjX2FsbCwgJyknKSkKYGBgClNlbnNpdGl2aXR5OiBUaGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgbW9kZWwgcHJlZGljdHMgYSBwb3NpdGl2ZSBvdXRjb21lIGZvciBhbiBvYnNlcnZhdGlvbiB3aGVuIGluZGVlZCB0aGUgb3V0Y29tZSBpcyBwb3NpdGl2ZS4KU3BlY2lmaWNpdHk6IFRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBtb2RlbCBwcmVkaWN0cyBhIG5lZ2F0aXZlIG91dGNvbWUgZm9yIGFuIG9ic2VydmF0aW9uIHdoZW4gaW5kZWVkIHRoZSBvdXRjb21lIGlzIG5lZ2F0aXZlLgoKCgoKIyBDb21wYXJpbmcgYmVoYXZpb3Igb2YgYWxsIHRlc3Rpbmcgc2V0cyBjb21iaW5lZCAoY2x1c3RlcmluZyBhbmFseXNlcykKCmBgYHtyfQp2aXJhbF9zY29yZXMgPC0gbWF0cml4KGRhdGE9MCwgbnJvdz1ucm93KHZpcnVzZXMpLCBuY29sPW5yb3coY29tYm9zX2xpc3QpKQpudW1fdmlydXNlcyA8LSBkYXRhLmZyYW1lKHRvb2xjb21ibz1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV92aXJ1c2VzPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSkpCgpmb3IgKGkgaW4gMTpucm93KGNvbWJvc19saXN0KSkgewogIHZpcmFsX3Njb3Jlc1ssaV0gPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX3ZpYnJhbnQgPSBjb21ib3NfbGlzdCRWSUJSQU5UW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gY29tYm9zX2xpc3QkVlNbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gY29tYm9zX2xpc3QkVlMyW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nID0gY29tYm9zX2xpc3QkQ2hlY2tWW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfa2FpanUgPSBjb21ib3NfbGlzdCRLYWlqdVtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBjb21ib3NfbGlzdCREVkZbaV0pCiAgCiAgbnVtX3ZpcnVzZXMkbnVtX3ZpcnVzZXNbaV0gPC0gdGFibGUodmlyYWxfc2NvcmVzWyxpXT49MSlbWzJdXQogIAogIG51bV92aXJ1c2VzJHRvb2xjb21ib1tpXSA8LSBjb21ib3NfbGlzdCR0b29sY29tYm9baV0KICAKICBudW1fdmlydXNlcyR0b29sY29tYm8yW2ldIDwtIGNvbWJvc19saXN0JHRvb2xjb21ibzJbaV0KfQoKbnVtX3ZpcnVzZXMkbnVtdG9vbHMgPC0gc3RyX2NvdW50KG51bV92aXJ1c2VzJHRvb2xjb21ibywgIjEiKQpudW1fdmlydXNlcyA8LSBudW1fdmlydXNlc1tvcmRlcihudW1fdmlydXNlcyRudW1fdmlydXNlcywgZGVjcmVhc2luZz1GKSxdCm51bV92aXJ1c2VzJHRvb2xjb21ibyA8LSBmYWN0b3IobnVtX3ZpcnVzZXMkdG9vbGNvbWJvLCBsZXZlbHMgPSB1bmlxdWUobnVtX3ZpcnVzZXMkdG9vbGNvbWJvKSkKbnVtX3ZpcnVzZXMkdG9vbGNvbWJvMiA8LSBmYWN0b3IobnVtX3ZpcnVzZXMkdG9vbGNvbWJvMiwgbGV2ZWxzID0gdW5pcXVlKG51bV92aXJ1c2VzJHRvb2xjb21ibzIpKQpudW1fdmlydXNlcyRudW10b29scyA8LSBhcy5mYWN0b3IobnVtX3ZpcnVzZXMkbnVtdG9vbHMpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QobnVtX3ZpcnVzZXMsIGFlcyh4PXRvb2xjb21ibywgeT1udW1fdmlydXNlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW10b29scywgZmlsbD1udW10b29scykpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uIChDViwgRFZGLCBLSiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIk51bSBWaXJ1c2VzIFByZWRpY3RlZCIpCgpnZ3Bsb3QobnVtX3ZpcnVzZXMsIGFlcyh4PXRvb2xjb21ibzIsIHk9bnVtX3ZpcnVzZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtdG9vbHMsIGZpbGw9bnVtdG9vbHMpKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAoQ1YsIERWRiwgS0osIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJOdW0gVmlydXNlcyBQcmVkaWN0ZWQiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QobnVtX3ZpcnVzZXMsIGFlcyh4PW51bXRvb2xzLCB5PW51bV92aXJ1c2VzKSkgKwogIGdlb21fYm94cGxvdChhZXMoY29sb3I9bnVtdG9vbHMpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9bnVtdG9vbHMsIGZpbGw9bnVtdG9vbHMpKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBUb29scyIpICsKICB5bGFiKCJOdW0gVmlydXNlcyBQcmVkaWN0ZWQiKQpgYGAKCgpgYGB7cn0KdmlyYWxfc2NvcmVzX25vemVyb3MgPC0gdmlyYWxfc2NvcmVzW3Jvd1N1bXModmlyYWxfc2NvcmVzKT4wLF0KdmlyYWxfc2NvcmVzX25vemVyb3MgPC0gdmlyYWxfc2NvcmVzX25vemVyb3MgKyAxCnZpcmFsX3Njb3Jlc19ub3plcm9zIDwtIGFzLmRhdGEuZnJhbWUodmlyYWxfc2NvcmVzX25vemVyb3MpCgpjb2xuYW1lcyh2aXJhbF9zY29yZXNfbm96ZXJvcykgPC0gbnVtX3ZpcnVzZXMkdG9vbGNvbWJvMgpgYGAKCmBgYHtyfQpsaWJyYXJ5KHBoeWxvc2VxKQpgYGAKCgpgYGB7cn0KdG9vbGRhdGEgPC0gbnVtX3ZpcnVzZXMKCnJvd25hbWVzKHRvb2xkYXRhKSA8LSB0b29sZGF0YSR0b29sY29tYm8yCmBgYAoKYGBge3J9CnBoeXNlcV9wb29sZWQgPC0gcGh5bG9zZXEob3R1X3RhYmxlKHZpcmFsX3Njb3Jlc19ub3plcm9zLCB0YXhhX2FyZV9yb3dzID0gVCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfZGF0YSh0b29sZGF0YSkpCmBgYAoKYGBge3J9Cm9yZGluYXRpb24gPC0gcGh5bG9zZXE6Om9yZGluYXRlKHBoeXNlcSA9cGh5c2VxX3Bvb2xlZCwgbWV0aG9kID0gIlBDb0EiLCBkaXN0YW5jZSA9ICJicmF5IikKcGh5bG9zZXE6OnBsb3Rfb3JkaW5hdGlvbihwaHlzZXEgPSBwaHlzZXFfcG9vbGVkLCBvcmRpbmF0aW9uID0gb3JkaW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICBzaGFwZT0ibnVtdG9vbHMiLCBjb2xvcj0ibnVtX3ZpcnVzZXMiKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsKICB0aGVtZV9idygpICsKICBnZW9tX2xhYmVsKGxhYmVsPXRvb2xkYXRhJHRvb2xjb21ibykKCnBoeWxvc2VxOjpwbG90X29yZGluYXRpb24ocGh5c2VxID0gcGh5c2VxX3Bvb2xlZCwgb3JkaW5hdGlvbiA9IG9yZGluYXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hhcGU9Im51bXRvb2xzIiwgY29sb3I9Im51bV92aXJ1c2VzIikgKyAKICBnZW9tX3BvaW50KHNpemUgPSAzKSArCiAgdGhlbWVfYncoKQpgYGAKdG8gZG86IHRyeSBjb2xvcmluZyBhYm92ZSBiYXNlZCBvbiB0aGUgRjEgc2NvcmVzIG9mIHRoZSB0ZXN0aW5nIHNldCBvbiBlYWNoIGNvbWJpbmF0aW9uCgpgYGB7cn0KYnJheV9kaXN0IDwtIHBoeWxvc2VxOjpkaXN0YW5jZShwaHlzZXFfcG9vbGVkLCBtZXRob2Q9ImJyYXkiKQpjbHVzdGVycyA8LSBoY2x1c3QoZGlzdChicmF5X2Rpc3QpKQpwbG90KGNsdXN0ZXJzKQoKbXljbHVzdGVycyA8LSBjdXRyZWUoY2x1c3RlcnMsIGg9MS4xKQpgYGAKCgpgYGB7cn0KbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT0xXSkKbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT0yXSkKbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT0zXSkKbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT00XSkKbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT01XSkKCm15Y2x1c3RlcnNfZGYgPC0gdGliYmxlKGNvbWJvPW5hbWVzKG15Y2x1c3RlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9pbmRleD1teWNsdXN0ZXJzKQoKbXljbHVzdGVyc19kZiA8LSBzZXBhcmF0ZShteWNsdXN0ZXJzX2RmLCBjb2w9Y29tYm8sIGludG89YygiQ2hlY2tWIiwgIkRWRiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJLYWlqdSIsICJWSUJSQU5UIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlZpclNvcnRlciIsICJWaXJTb3J0ZXIyIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwPSIgIiwgcmVtb3ZlID0gRikKCgp0b29sX2NvdW50IDwtIGFzLmRhdGEuZnJhbWUocmJpbmQodGFibGUobXljbHVzdGVyc19kZiRDaGVja1YsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlKG15Y2x1c3RlcnNfZGYkRFZGLCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJEthaWp1LCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJFZJQlJBTlQsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlKG15Y2x1c3RlcnNfZGYkVmlyU29ydGVyLCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJFZpclNvcnRlcjIsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdKQogICAgICAgICAgICAgICAgICAgICkKCnRvb2xfY291bnQkbWV0aG9kIDwtIGMoIkNoZWNrViIsICJEVkYiLCAiS2FpanUiLCAiVklCUkFOVCIsICJWaXJTb3J0ZXIiLCAiVmlyU29ydGVyMiIpCgp0b29sX2NvdW50IDwtIG1lbHQodG9vbF9jb3VudCkKCmNvbG5hbWVzKHRvb2xfY291bnQpIDwtIGMoInRvb2wiLCAiY2x1c3Rlcl9pbmRleCIsICJ0b29sX2NvdW50IikKYGBgCgpgYGB7cn0KcGFsIDwtIGdndGhlbWVzOjp0YWJsZWF1X2NvbG9yX3BhbChwYWxldHRlPSJUYWJsZWF1IDEwIiwgdHlwZT0icmVndWxhciIpCgpnZ3Bsb3QodG9vbF9jb3VudCwgYWVzKHg9Y2x1c3Rlcl9pbmRleCwgeT10b29sX2NvdW50LAogICAgICAgICAgICAgICAgICAgZmlsbD1jbHVzdGVyX2luZGV4LAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2x1c3Rlcl9pbmRleCkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKSArCiAgeGxhYigiQ2x1c3RlciIpICsKICB5bGFiKCJOdW1iZXIgb2YgVGltZXMgaW4gQ2x1c3RlciIpICsgCiAgZmFjZXRfd3JhcCh+dG9vbCwgc2NhbGVzID0gImZyZWUiKQpgYGAKCgo=